package main

import (
	"errors"
	"flag"
	dns "github.com/alibabacloud-go/alidns-20150109/v2/client"
	openapi "github.com/alibabacloud-go/darabonba-openapi/client"
	"github.com/alibabacloud-go/tea/tea"
	log "github.com/sirupsen/logrus"
	"net"
	"os"
	"strings"
	"time"
)

var client *dns.Client

func NewString(s string) *string {
	return &s
}

func Init(accessKey, secretKey, logLevel string) {
	log.SetFormatter(&log.TextFormatter{
		FullTimestamp:   true,
		TimestampFormat: "2006-01-02 15:04:05.000",
	})
	var err error
	level, err := log.ParseLevel(logLevel)
	if err != nil {
		log.Errorf("parse log level error:%v\n", err)
		os.Exit(1)
	}
	log.SetLevel(level)
	client, err = dns.NewClient(&openapi.Config{
		AccessKeyId:     NewString(accessKey),
		AccessKeySecret: NewString(secretKey),
		RegionId:        NewString(""),
	})
	if err != nil {
		log.Errorf("create client error:%v\n", err)
		os.Exit(1)
	}
}

func GetRecordId(DNS string) string {
	info := strings.Split(DNS, ":")
	if len(info) != 3 {
		log.Errorf("DNS format error: %s. Need to be like domain:record:type\n", DNS)
		os.Exit(1)
	}
	rsp, err := client.DescribeDomainRecords(&dns.DescribeDomainRecordsRequest{
		DomainName: NewString(info[0]),
		RRKeyWord:  NewString(info[1]),
		Type:       NewString(info[2]),
	})
	if err != nil {
		log.Errorf("DescribeDomainRecords error:%v\n", err)
		os.Exit(1)
	}
	var record *dns.DescribeDomainRecordsResponseBodyDomainRecordsRecord
	var recordId string
	if rsp != nil && rsp.Body != nil && rsp.Body.DomainRecords != nil && len(rsp.Body.DomainRecords.Record) > 0 {
		record = rsp.Body.DomainRecords.Record[0]
		recordId = *record.RecordId
	}
	return recordId
}

func GetOSIPv6(interfaceName string) string {
	ifaces, err := net.Interfaces()
	if err != nil {
		log.Error("Error getting interfaces:", err)
		os.Exit(1)
	}

	var ifacesNames []string
	for _, iface := range ifaces {
		ifacesNames = append(ifacesNames, iface.Name)
		if iface.Name == interfaceName {
			addrs, err := iface.Addrs()
			if err != nil {
				log.Error("Error getting addresses:", err)
				os.Exit(1)
			}
			var address []string
			for _, addr := range addrs {
				if ipNet, ok := addr.(*net.IPNet); ok && ipNet.IP.To16() != nil && ipNet.IP.To4() == nil {
					address = append(address, ipNet.IP.String())
				}
			}
			if len(address) > 0 {
				log.Info("there are many address in interface: ", address)
				for _, a := range address {
					if strings.HasPrefix(a, "2409") {
						return a
					}
				}
				log.Errorf("can not find Global IPv6 address Prefix 2409 in interface: %s with addresss %v\n", interfaceName, address)
				os.Exit(1)
			}
		}
	}
	log.Errorf("interface with name [%s] not found in system interface %v\n", interfaceName, ifacesNames)
	os.Exit(1)
	return ""
}

func UpdateDNS(DNS string) bool {
	info := strings.Split(DNS, ":")
	if len(info) != 3 {
		log.Errorf("DNS format error: %s. Need to be like domain:record:type\n", DNS)
		os.Exit(1)
	}
	rsp, err := client.UpdateDomainRecord(&dns.UpdateDomainRecordRequest{
		RecordId: NewString(recordId),
		RR:       NewString(info[1]),
		Type:     NewString(info[2]),
		Value:    NewString(ipv6),
	})
	if err != nil {
		sdkErr := &tea.SDKError{}
		if errors.As(err, &sdkErr) {
			if *sdkErr.Code == "DomainRecordDuplicate" {
				log.Warnln("The DNS record already exists IP not change. no need to update")
				return true
			}
		}
		log.Errorf("DescribeDomainRecords error:%v\n", err)
		return false
	}
	if rsp != nil && rsp.Body != nil {
		return true
	}
	return false
}

var ipv6 string
var recordId string

func main() {
	var (
		accessKey     string
		secretKey     string
		interfaceName string
		DNS           string
		duration      int
		logLevel      string
	)

	flag.StringVar(&accessKey, "accessKey", "", "aliyun accessKey")
	flag.StringVar(&secretKey, "secretKey", "", "aliyun secretKey")
	flag.StringVar(&interfaceName, "interfaceName", "", "interface name")
	flag.StringVar(&DNS, "DNS", "", "format like domain:record:type")
	flag.IntVar(&duration, "duration", 10, "duration for check ipv6 address change")
	flag.StringVar(&logLevel, "logLevel", "info", "log level")
	flag.Parse()

	// 参数校验
	if accessKey == "" || secretKey == "" || interfaceName == "" || DNS == "" {
		flag.Usage()
		os.Exit(1)
	}

	// 最小间隔10s
	if duration < 10 {
		duration = 10
	}

	// 初始化
	Init(accessKey, secretKey, logLevel)

	// 获取recordID
	if recordId == "" {
		recordId = GetRecordId(DNS)
		if recordId == "" {
			log.Errorln("can not get dns record ID from aliyun ...")
			os.Exit(1)
		}
	}

	log.Infof("Auto update dns reslove for interface %s to DNS %s every %d seconds\n", interfaceName, DNS, duration)
	// IPv6 地址检测及更新
	for {
		ip := GetOSIPv6(interfaceName)
		if ip == "" {
			log.Errorf("can not get IPv6 address from interface %s\n", interfaceName)
			os.Exit(1)
		}
		if ip != ipv6 {
			ipv6 = ip
			log.Printf("IPv6 address change to %s\n", ipv6)
			if UpdateDNS(DNS) {
				log.Printf("Update DNS success\n")
			} else {
				log.Printf("Update DNS failed\n")
			}
		} else {
			log.Printf("IPv6 address not change ...")
		}
		time.Sleep(time.Duration(duration) * time.Second)
	}
}
